Typical software systems include an application program run by an operating system on a processor connected to a number of hardware peripherals. In some systems, such as those where the Java language is employed, there is additionally a virtual machine such as a Java virtual machine (JVM) situated between the operating system and the application program.
In order to facilitate communication with the hardware peripherals, nearly all complex software systems include a device driver which is specific to each hardware peripheral. These device drivers provide a layer of abstraction to their clients (the operating system and ultimately the application program) while allowing them to use the underlying hardware peripherals.
However, device drivers are notoriously difficult to debug or troubleshoot due to the asynchronous nature of their coupling with interrupts and due the lack of debugging features. A faulty driver can also inhibit user input and/or user output. At interrupt levels, the system cannot provide support for the common user state input/output functionality. Furthermore, since timing is often critical, it is impossible to stop a processor and trace device driver code in a non-destructive way. Also, because of the way systems are developed, frequently different development teams are responsible for different layers of a design. When interface problems develop, it is often difficult to determine where the problem originated and hence which team should fix the problem.
Consequently, device drivers take more time to develop and their opacity makes them more error-prone. One of the most common errors which may occur during driver development is an error in pointer arithmetic which instructs the processor to access an erroneous location. Systems generally allow this initially and operation resumes without any apparent disturbance until a later point in time where the value in question is used.
While this is not a big problem for large computing platforms with standardized peripheral interfaces, and a standardized layered architecture, it becomes a very serious problem for application specific hardware and devices where for each new design, the application peripheral path must be debugged from scratch.
Common safeguard measures against pointer arithmetic errors include software range checking. Some languages, such as Java, have inherent measures which prevent invalid memory accesses. However, using the built-in range checking of standard Java to develop device drivers is currently impossible as one of the fundamental characteristics of Java is that any client machine should be protected from corruption/bugs in Java, i.e. any bug in a Java application should only effect the Java application and should have no effect on other applications and memory unrelated to the Java application. To achieve this level of security, Java applications running on the JVM are not given direct access to memory. Instead, memory access is done through an indirection mechanism through the JVM.
Referring now to FIG. 1, a conventional embedded environment 3 typically has hardware 2, software in the form of native code 4 (or assembly language), and software in the form of a Java application 6. Also shown are externally connected hardware peripherals 8. The hardware 2 consists of a processor core 10, memory in the form of RAM 12 and/or ROM 14 and one or more physical interfaces 18 including for example a serial port 27. The software 4 running on the processor 10 includes an operating system 20 over top of which is run a Java virtual machine 22 as a task, and also over which other tasks such as an event dispatcher task 23 is run. The Java application 6 uses the resources and features of the Java virtual machine 22.
FIG. 1 also shows the details of a typical path from the Java application 6 to and from a particular hardware peripheral 8 which for the purpose of this example we will assume is the serial port peripheral 26 connected through the serial port 27. The Java application 6 includes functionality 28 for either generating data ultimately for output to the serial port 27, or for processing data ultimately received from the serial port 27. Of course the functionality 28 does not interact with the serial port 27 directly. The Java virtual machine 22 has a Java native interface 30 through which the Java application 6 communicates with the serial port physical interface 27. The operating system 20 has a serial port device driver 32 which has an input queue 34 and an output queue 36, through which it communicates with the underlying hardware 2. The serial port device driver 32 is typically run at interrupt level, or through a deferred procedure call within the operating system kernel (not shown). More specifically, the serial port device driver 32 communicates with the serial port 27 through serial port memory mapped registers 56 to an input queue 38 and an output queue 40 and on to the hardware peripheral 26. The operating system 20 also has an IRQ (interrupt request) handler 33 for each interrupt from any hardware peripheral.
When the Java application 6 has to communicate with the hardware peripherals 8 and in this case the serial port peripheral 26, a path such as that consisting of the serial port communications 28→Java virtual machine 22→Java native interface 30→operating system 20→serial port device driver 32→serial port physical interface 27→serial port hardware peripheral 26 must be established and debugged for each different hardware peripheral. More specifically, when the Java application 6 has data to send to the serial port peripheral 28, the Java application 6 communicates with the device driver 32 using the Java native interfaces 30. The JNI 30 takes the data, formats it and passes it on to the device driver 32 by copying it into the output buffer 34. The serial port device driver 32 transfers the data to the serial port memory mapped registers 56 of the serial port 27. These are copied into the hardware queue 38 in the serial port 27 for output.
When the serial port 27 receives data destined for the Java application 6, an even more complicated path is taken. For communication originating from the hardware, the process typically goes as follows. To begin, the arrival of data at the serial port 27 triggers the assertion of a hardware interrupt. When this occurs, the program flow is interrupted, and the IRQ handler 33 starts an interrupt service routine. The interrupt service routine calls the serial port device driver 32 which reads the data from the hardware input queue 40 in the serial port 27 and copies it into the input queue 36 which is one of the device driver's data structures. The serial port device driver 32 then posts an event to the event dispatcher task 23. The interrupt service routine returns and normal Java operation resumes. The event dispatcher task 23 sends an event to one of the destination threads 6 to read from the input queue 36 of the serial port device driver 32, for example through a piping mechanism provided by the JNI 30.
It can be clearly seen that there are a large number of areas where bugs may make their way into the design of such a Java application—hardware peripheral interaction. Furthermore, each copying stage forces power consuming and processor intensive operations which are inevitable due to the abstractions of the operating system 20 and the JVM 22.